/**
 * 
 */
package truerefactor.genetic;

import java.util.LinkedList;
import java.util.List;

/**
 * Individual - The chromosomal representation of a member of a population for
 * the GA. Each individual is comprised of a Fitness object which represents
 * both the components of the fitness and the total fitness. A GASmell object
 * which comprises the total code smell and its component measurements. And a
 * set of Alleles representing the choromosome.
 * 
 * @author Isaac Griffith
 */
public class Individual {

    /**
     * The list of alleles that compose this individual
     */
    private List<Allele> chromosome;
    /**
     * The encapsulation of this individuals fitness
     */
    private Fitness fitness;
    /**
     * The encapsulation of this individuals code smell
     */
    private GASmell smell;
    /**
     * The maximum number of component fitness values to be associated with this
     * individual
     */
    private int maxFitnessSize;
    /**
     * The maximum number of component code smell values to be associated with
     * this individual
     */
    private int maxSmellSize;

    /**
     * Creates a new instance of Individual with the provided maximum number of
     * component fitness values and maximum number of component code smell
     * values
     * 
     * @param maxFitnessIndex
     *            maximum number of component fitness values
     * @param maxSmellIndex
     *            maximum number of component code smell values
     */
    public Individual(int maxFitnessIndex, int maxSmellIndex)
    {
        chromosome = new LinkedList<Allele>();
        fitness = new Fitness(maxFitnessIndex);
        smell = new GASmell(maxSmellIndex);
        this.maxFitnessSize = maxFitnessIndex;
        this.maxSmellSize = maxSmellIndex;
    }

    /**
     * Adds the provided allele to this individual's chromosome
     * 
     * @param allele
     *            the Allele to be added
     */
    public void addAllele(Allele allele)
    {
        chromosome.add(allele);
    }

    /**
     * courtesy of Scott Wahl Crosses over the provided Individual with this
     * Individual to generate a set of children.
     * 
     * @param individual
     * @return
     */
    public List<Individual> crossover(Individual individual)
    {
        List<Individual> children = new LinkedList<Individual>();

        children.add(new Individual(maxFitnessSize, maxSmellSize));
        children.add(new Individual(maxFitnessSize, maxSmellSize));

        // set the minSize to be the length of the shortest individual and
        // maxSize to be the length of the longest individual
        int minSize = (getSize() < individual.getSize()) ? getSize() : individual.getSize();
        int maxSize = (getSize() < individual.getSize()) ? individual.getSize() : getSize();

        int i = 0;
        for (; i < minSize; i++)
        {
            if (Math.random() < 0.5)
            {
                children.get(0).addAllele(getAllele(i));
                children.get(1).addAllele(individual.getAllele(i));
            }
            else
            {
                children.get(0).addAllele(individual.getAllele(i));
                children.get(1).addAllele(getAllele(i));
            }
        }

        for (; i < maxSize; i++)
        {
            if (Math.random() < 0.5)
                children.get(0).addAllele((i < getSize() ? getAllele(i) : individual.getAllele(i)));
            else
                children.get(1).addAllele((i < getSize() ? getAllele(i) : individual.getAllele(i)));
        }
        return children;
    }

    /**
     * Returns the allele at the given index in this individual's chromosome
     * 
     * @param index
     *            index of the requested allele
     * @return the requested allele at the provided index of the choromosome.
     *         Note that indexes begin at zero. If an index < 0 or greater than
     *         the number of contained alleles is provided then a null is
     *         returned.
     */
    public Allele getAllele(int index)
    {
        if (index < 0 || index >= chromosome.size())
            return null;
        else
            return chromosome.get(index);
    }

    /**
     * @return The chromosome of the individual. In other words it returns all
     *         contained alleles.
     */
    public List<Allele> getChromosome()
    {
        return chromosome;
    }

    /**
     * @return returns the Fitness object used to record the fitness of this
     *         Individual
     */
    public Fitness getFitness()
    {
        return fitness;
    }

    /**
     * @return rturns the GASmell object used to record the codesmell values of
     *         this Individual
     */
    public GASmell getSmells()
    {
        return smell;
    }

    /**
     * @return returns the current size of this Individual's current chromosome
     */
    public int getSize()
    {
        return chromosome.size();
    }

    /**
     * Mutate's this allele based on the mutation type, index of the allele to
     * mutate, and if necessary complete replacement with the provided Allele
     * 
     * @param type
     *            If 0 the allele at the provided index will be removed, if 1
     *            then the provided allele will be inserted at the provided
     *            index, and if 2 the allele with the provided index will be
     *            replaced by the provided allele
     * @param index
     *            index of the allele to be mutated
     * @param allele
     *            the new allele to be inserted or to replace an existing allele
     */
    public void mutate(int type, int index, Allele allele)
    {
        switch (type)
        {
        case 0:
            chromosome.remove(index);
            break;
        case 1:
            chromosome.add(index, allele);
            break;
        case 2:
            chromosome.remove(index);
            chromosome.add(index, allele);
            break;
        }
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("Individual Fitness %f\n", fitness.getTotal()));
        for (int i = 0; i < chromosome.size(); i++)
        {
            sb.append("\t" + (i + 1) + "." + chromosome.get(i));
        }
        return sb.toString();
    }

    /**
     * Adds the provided value to the fitness object representing the fitness of
     * this Individual
     * 
     * @param value
     *            value to be added
     */
    public void addFitnessValue(double value)
    {
        fitness.addIndividualVal(value);
    }

    /**
     * @return the total value of the fitness for this individual
     */
    public double getTotalFitness()
    {
        return fitness.getTotal();
    }

    /**
     * @return the total value of the code smells measured for this individual
     */
    public double getTotalSmell()
    {
        return smell.getTotal();
    }

    /**
     * Adds the provided value to the GASmell object recording code smell values
     * for this Individual.
     * 
     * @param value
     *            value to be added to the code smell recording object
     *            associated with this individual
     */
    public void addSmellValue(double value)
    {
        smell.addIndividualVal(value);
    }
}
